/*
 *
 */
package org.jiscinvolve.astrodabis.barberry.hibernate.core;

import java.util.List;
import java.util.Iterator;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import java.io.Serializable;

import javax.persistence.Id;
import javax.persistence.Table;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GenerationType;
import javax.persistence.GeneratedValue;
import javax.persistence.UniqueConstraint;

import org.hibernate.annotations.Index;
import org.hibernate.annotations.NamedQuery;
import org.hibernate.annotations.NamedQueries;
import org.hibernate.annotations.GenericGenerator;

import org.springframework.stereotype.Repository;

import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Primary;

import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional; 

import org.jiscinvolve.astrodabis.barberry.common.core.Job;
import org.jiscinvolve.astrodabis.barberry.common.core.Tag;
import org.jiscinvolve.astrodabis.barberry.common.core.Womble;
import org.jiscinvolve.astrodabis.barberry.common.core.Account;
import org.jiscinvolve.astrodabis.barberry.common.core.Identifier;
import org.jiscinvolve.astrodabis.barberry.common.core.LongIdent;
import org.jiscinvolve.astrodabis.barberry.common.core.SimpleIdent;
import org.jiscinvolve.astrodabis.barberry.common.core.AbstractIdent;

import org.jiscinvolve.astrodabis.barberry.common.core.exception.*;

import org.jiscinvolve.astrodabis.barberry.common.data.RowTag;
import org.jiscinvolve.astrodabis.barberry.common.data.JoinTag;
import org.jiscinvolve.astrodabis.barberry.hibernate.annotation.*;

/**
 *
 */
@Entity
@Table(
    name = TagEntity.DB_TABLE_NAME,
    uniqueConstraints = @UniqueConstraint(
        columnNames = {
            OwnedEntity.DB_OWNER_COL,
            TagEntity.DB_NAME_COL
            }
        )
    )
@NamedQueries(
        {
        @NamedQuery(
            name  = "tag-all",
            query = "FROM TagEntity ORDER BY name asc, ident asc"
            ),
        @NamedQuery(
            name  = "tag-name",
            query = "FROM TagEntity WHERE name = :name ORDER BY ident asc"
            ),
        @NamedQuery(
            name  = "tag-owner",
            query = "FROM TagEntity WHERE owner = :owner ORDER BY ident asc, name asc"
            ),
        @NamedQuery(
            name  = "tag-owner-name",
            query = "FROM TagEntity WHERE owner = :owner AND name = :name ORDER BY ident asc"
            ),
        @NamedQuery(
            name  = "tag-recent-all",
            query = "FROM TagEntity ORDER BY ident desc, name asc"
            ),
        @NamedQuery(
            name  = "tag-recent-owner",
            query = "FROM TagEntity WHERE owner = :owner ORDER BY ident desc, name asc"
            ),

        @NamedQuery(
            name  = "tag-rowtag-job",
            query = "SELECT DISTINCT rowtag.tag FROM RowTagEntity as rowtag WHERE rowtag.job = :job ORDER BY rowtag.tag asc"
            ),
        @NamedQuery(
            name  = "tag-jointag-job",
            query = "SELECT DISTINCT jointag.tag FROM JoinTagEntity as jointag WHERE jointag.job = :job ORDER BY jointag.tag asc"
            )
        }
    )
public class TagEntity
extends OwnedEntity
implements Tag
    {

    /**
     * Our debug logger.
     * 
     */
    private static Log log = LogFactory.getLog(TagEntity.class);

    public static final String DB_TABLE_NAME = "tags"  ;
    public static final String DB_IDENT_COL  = "ident" ;
    public static final String DB_NAME_COL   = "name"  ;
    public static final String DB_TEXT_COL   = "text"  ;

    @Repository
    public static class Factory
    extends AbstractFactory<Tag, TagEntity>
    implements Tag.Factory
        {

        @Override
        public String clean(String string)
            {
            if (string != null)
                {
                return string.toLowerCase().replaceAll(
                    Tag.NAME_EXCLUDE,
                    Tag.NAME_REPLACE
                    );
                }
            else {
                return null ;
                }
            }

        @Override
        public Identifier ident(String value)
            {
            return LongIdent.create(
                value
                );
            }

        @Override
        @CreateObject
        public Tag create(Account owner, String name)
            {
            return createQuick(
                owner,
                name
                );
            }

        @Override
        @CreateQuick
        public Tag createQuick(Account owner, String name)
            {
            Tag found = selectQuick(
                owner,
                name
                );
            if (found != null)
                {
                return found ;
                }
            else {
                return hibernate().insert(
                    new TagEntity(
                        owner,
                        clean(
                            name
                            )
                        )
                    );
                }
            }

        @Override
        @CreateObject
        public Tag create(Account owner, String name, String text)
            {
            return createQuick(
                owner,
                name,
                text
                );
            }

        @Override
        @CreateQuick
        public Tag createQuick(Account owner, String name, String text)
            {
            Tag found = selectQuick(
                owner,
                name
                );
// TODO
// This should behave the same as create(owner,name)
            if (found != null)
                {
                throw new DuplicateEntryException() ;
                }
            else {
                return hibernate().insert(
                    new TagEntity(
                        owner,
                        clean(
                            name
                            ),
                        text
                        )
                    );
                }
            }

        @Override
        @SelectObject
        public Iterable<Tag> select()
            {
            return hibernate().iterable(
                hibernate().query(
                    "tag-all"
                    )
                );
            }

        @Override
        @SelectObject
        public Tag select(Identifier ident)
            {
            return hibernate().select(
                TagEntity.class,
                ident
                );
            }

        @Override
        @SelectObject
        public Iterable<Tag> select(String name)
            {
            return hibernate().iterable(
                hibernate().query(
                    "tag-name"
                    ).setString(
                        "name",
                        clean(
                            name
                            )
                        )
                );
            }

        @Override
        @SelectObject
        public Iterable<Tag> select(Account owner)
            {
            return hibernate().iterable(
                hibernate().query(
                    "tag-owner"
                    ).setEntity(
                        "owner",
                        owner
                        )
                );
            }

        @Override
        @SelectObject
        public Tag select(Account owner, String name)
            {
            return selectQuick(
                owner,
                name
                );
            }

        @SelectQuick
        public Tag selectQuick(Account owner, String name)
            {
            return hibernate().single(
                hibernate().query(
                    "tag-owner-name"
                    ).setEntity(
                        "owner",
                        owner
                        ).setString(
                            "name",
                            clean(
                                name
                                )
                            )
                );
            }

        @Override
        @UpdateObject
        public void update(Tag tag)
            {
            hibernate().update(
                tag
                );
            }

        @Override
        @UpdateObject
        public void delete(Tag tag)
            {
            //
            // Delete the associated rowtags.
            Womble.womble().rowtags().delete(
                tag
                );
            //
            // Delete the associated jointags.
            Womble.womble().jointags().delete(
                tag
                );
            //
            // Delete the tag.
            hibernate().delete(
                tag
                );
            }

        @Override
        @SelectObject
        public Iterable<Tag> recent(int limit)
            {
            return hibernate().iterable(
                hibernate().query(
                    "tag-recent-all"
                    ).setMaxResults(
                        limit
                        )
                );
            }

        @Override
        @SelectObject
        public Iterable<Tag> recent(Account owner, int limit)
            {
            return hibernate().iterable(
                hibernate().query(
                    "tag-recent-owner"
                    ).setEntity(
                        "owner",
                        owner
                        ).setMaxResults(
                            limit
                            )
                );
            }

        public Iterable<Tag> select(Job job)
            {
// Easier if RowTag and JoinTag had a common parent.
            if (job.annotType() == Job.AnnotType.SINGLE)
                {
                return hibernate().iterable(
                    hibernate().query(
                        "tag-rowtag-job"
                        ).setEntity(
                            "job",
                            job
                            )
                    );
                }
            else {
                return hibernate().iterable(
                    hibernate().query(
                        "tag-jointag-job"
                        ).setEntity(
                            "job",
                            job
                            )
                    );
                }
            }
        }

    protected TagEntity()
        {
        super();
        }

    protected TagEntity(Account owner, String name)
        {
        this(
            owner,
            name,
            null
            );
        }
        
    protected TagEntity(Account owner, String name, String text)
        {
        super(
            owner
            );
        this.name = name ;
        this.text = text ;
        }

    @Id
    @Column(
        name = DB_IDENT_COL,
        unique = true,
        nullable = false,
        updatable = false
        )
    @GeneratedValue(
        generator="system-hilo"
        )
    @GenericGenerator(
        name="system-hilo",
        strategy="hilo"
        )
    private Long ident ;

    @Override
    public Identifier ident()
        {
        return new AbstractIdent()
            {
            @Override
            public Serializable value()
                {
                return ident ;
                }
            };
        }

    @Column(
        name = DB_NAME_COL,
        unique = false,
        nullable = false,
        updatable = false
        )
    private String name ;

    @Override
    public String name()
        {
        if (this.name == null)
            {
            return this.ident.toString() ;
            }
        else {
            return this.name ;
            }
        }

    @Column(
        name = DB_TEXT_COL,
        unique = false,
        nullable = true,
        updatable = true
        )
    private String text ;

    @Override
    public String text()
        {
        if (this.text == null)
            {
            return "" ;
            }
        else {
            return this.text ;
            }
        }

    @Override
    public void text(String text)
        {
        this.text = text ;
        }

    @Override
    public boolean equals(Object that)
        {
        if (this == that)
            {
            return true ;
            }
        if (this.ident == null)
            {
            return false ;
            }
        if (that instanceof TagEntity)
            {
            return this.ident.equals(
                ((TagEntity)that).ident
                );
            }
        if (that instanceof Tag)
            {
            return this.ident().equals(
                ((Tag)that).ident()
                );
            }
        return false ;
        }

    @Override
    public String toString()
        {
        StringBuilder builder = new StringBuilder();
        builder.append(
            "Tag["
            );
        builder.append(
            this.ident
            );
        builder.append(
            "]{ "
            );

        builder.append(
            this.owner().toString()
            );

        builder.append(
            ", name{"
            );
        builder.append(
            this.name()
            );
        builder.append(
            "}}"
            );
        return builder.toString();
        }

    public RowTags rowtags()
        {
        return new RowTags()
            {
            public long count()
                {
                return 0 ;
                }

            public Iterable<RowTag> select()
                {
                return Womble.womble().rowtags().select(
                    TagEntity.this
                    );
                }
            };
        }

    public JoinTags jointags()
        {
        return new JoinTags()
            {
            public long count()
                {
                return 0 ;
                }

            public Iterable<JoinTag> select()
                {
                return Womble.womble().jointags().select(
                    TagEntity.this
                    );
                }
            };
        }


    public Jobs jobs()
        {
        return new Jobs()
            {
            public Iterable<Job> rowtags()
                {
                return Womble.womble().jobs().rowtags(
                    TagEntity.this
                    );
                }

            public Iterable<Job> jointags()
                {
                return Womble.womble().jobs().jointags(
                    TagEntity.this
                    );
                }
            };
        }

    @Override
    public void delete()
        {
        log.debug("TagEntity.delete()");
        Womble.womble().tags().delete(
            this
            );
        }
    }

